package edu.caece.langprocessor;

import java.io.Reader;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;

import edu.caece.langprocessor.lexical.tokens.ErrorLexicalToken;
import edu.caece.langprocessor.semantic.SimpleSymbolsTable;
import edu.caece.langprocessor.syntax.SyntaxProcessor;
import edu.caece.langprocessor.syntax.Terminal;
import edu.caece.langprocessor.syntax.tree.CodeGeneratorTreeVisitor;
import edu.caece.langprocessor.syntax.tree.ParseTreeInheritAttributesVisitor;
import edu.caece.langprocessor.syntax.tree.ParseTreePrettyPrinterVisitor;
import edu.caece.langprocessor.syntax.tree.ParseTreeSynthAttributesVisitor;
import edu.caece.langprocessor.syntax.tree.Tree;
import edu.caece.langprocessor.syntax.tree.TreeNode;

public class Processor {

	private static final Log LOGGER = LogFactory.getLog(Processor.class);

	private SyntaxProcessor syntaxProcessor;
	private Boolean status;
	private String generatedCode;
	private String formattedCode;

	private ArrayList<String> semanticErrorsList;

	private ArrayList<String> semanticWarningsList;
	private SimpleSymbolsTable symTable;
	@Autowired
	public Processor(SyntaxProcessor syntaxProcessor) {
		this.syntaxProcessor = syntaxProcessor;
		this.semanticErrorsList = new ArrayList<String>();
		this.semanticWarningsList = new ArrayList<String>();
		this.symTable = new SimpleSymbolsTable();
	}

	public String getFormattedCode() {
		return formattedCode;
	}

	public String getGeneratedCode() {
		return this.generatedCode;
	}

	public ArrayList<String> getSemanticErrorsList() {
		return semanticErrorsList;
	}

	public ArrayList<String> getSemanticWarningsList() {
		return semanticWarningsList;
	}

	public Boolean getStatus() {
		return this.status;
	}

	public void proccess(Reader r) {
		LOGGER.info("Processor Started OK");

		this.syntaxProcessor.getLexicalProcessor().setCodeReader(r);
		this.status = this.syntaxProcessor.run();
		Tree parseTree = this.syntaxProcessor.getParseTree();

		if (status) {
			TreeNode rootNode = parseTree.getRoot();

			rootNode.setLevel(0);
			rootNode.setSymTable(symTable);
			rootNode.setErrorsList(semanticErrorsList);
			rootNode.setWarningsList(semanticWarningsList);

			ParseTreeInheritAttributesVisitor iav = new ParseTreeInheritAttributesVisitor(
					parseTree);
			iav.inherit();
			ParseTreeSynthAttributesVisitor sav = new ParseTreeSynthAttributesVisitor(
					parseTree);
			sav.synth();

			showCorrectCompileResults(symTable, semanticErrorsList,
					semanticWarningsList);

			ParseTreePrettyPrinterVisitor ptv = new ParseTreePrettyPrinterVisitor(
					parseTree);
			this.formattedCode = ptv.print();
			
			LOGGER.debug("Code:\n" + this.formattedCode);

			CodeGeneratorTreeVisitor cgv = new CodeGeneratorTreeVisitor(
					parseTree, this.symTable);
			this.generatedCode = cgv.generateCode();
			
			LOGGER.info("Compilation Success");
			
		} else {
			Integer errorCount = this.syntaxProcessor.getErrorCount();
			List<Terminal> errorsList = this.syntaxProcessor.getErrorsList();
			showErrors(errorCount, errorsList);
		}

	}

	private void showCorrectCompileResults(SimpleSymbolsTable symTable,
			ArrayList<String> semanticErrorsList,
			ArrayList<String> semanticWarningsList) {
		
		if (LOGGER.isInfoEnabled()) {
			LOGGER.info("Syntax processor result: PASS ");

			LOGGER.info("Symbols Table: " + symTable.toString());

			StringBuilder str = new StringBuilder();
			str.append("Semantic processor result \n");
			str.append("Found " + semanticErrorsList.size() + " errors.\n");
			str.append("Found " + semanticWarningsList.size()
					+ " warnings.\n");
			str.append("Messages summary:\n");
			for (String message : semanticErrorsList) {
				str.append(message + "\n");
			}

			for (String message : semanticWarningsList) {
				str.append(message + "\n");
			}

			LOGGER.info(str.toString());
		}
	}

	private void showErrors(Integer errorCount, List<Terminal> errorsList) {
		StringBuilder str = new StringBuilder();

		str
				.append("\n------------------------------------------------------------------------------------\n");
		str.append("Syntax processor result: FAIL\n");
		str.append("Found " + errorCount + " errors.\n");
		str.append("Errors summary:\n");
		for (Terminal t : errorsList) {
			// TODO Reemplazar por double dispatch o algo acorde
			if (t.getLexicalToken() instanceof ErrorLexicalToken) {
				str.append("\t* Invalid token: " + "Token: ");
			} else {
				str.append("\t* Unexpected token: " + "Token: ");
			}
			str.append(t.getLexicalToken().getToken() + " Lexema: "
					+ t.getLexicalToken().getLexema() + " Line: "
					+ t.getLexicalToken().getLine() + " Column: "
					+ t.getLexicalToken().getColumn() + "\n");
		}
		str
				.append("------------------------------------------------------------------------------------\n");
		LOGGER.info(str.toString());
	}
}
